home *** CD-ROM | disk | FTP | other *** search
/ Clickx 115 / Clickx 115.iso / software / tools / windows / tails-i386-0.16.iso / live / filesystem.squashfs / usr / share / iceweasel / modules / AutocompletePopup.jsm next >
Encoding:
Text File  |  2013-01-09  |  10.6 KB  |  405 lines

  1. /* vim: set ft=javascript ts=2 et sw=2 tw=80: */
  2. /* ***** BEGIN LICENSE BLOCK *****
  3.  * Version: MPL 1.1/GPL 2.0/LGPL 2.1
  4.  *
  5.  * The contents of this file are subject to the Mozilla Public License Version
  6.  * 1.1 (the "License"); you may not use this file except in compliance with
  7.  * the License. You may obtain a copy of the License at
  8.  * http://www.mozilla.org/MPL/
  9.  *
  10.  * Software distributed under the License is distributed on an "AS IS" basis,
  11.  * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
  12.  * for the specific language governing rights and limitations under the
  13.  * License.
  14.  *
  15.  * The Original Code is Autocomplete Popup.
  16.  *
  17.  * The Initial Developer of the Original Code is
  18.  * The Mozilla Foundation.
  19.  * Portions created by the Initial Developer are Copyright (C) 2011
  20.  * the Initial Developer. All Rights Reserved.
  21.  *
  22.  * Contributor(s):
  23.  *   Mihai Sucan <mihai.sucan@gmail.com> (original author)
  24.  *
  25.  * Alternatively, the contents of this file may be used under the terms of
  26.  * either the GNU General Public License Version 2 or later (the "GPL"), or
  27.  * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
  28.  * in which case the provisions of the GPL or the LGPL are applicable instead
  29.  * of those above. If you wish to allow use of your version of this file only
  30.  * under the terms of either the GPL or the LGPL, and not to allow others to
  31.  * use your version of this file under the terms of the MPL, indicate your
  32.  * decision by deleting the provisions above and replace them with the notice
  33.  * and other provisions required by the GPL or the LGPL. If you do not delete
  34.  * the provisions above, a recipient may use your version of this file under
  35.  * the terms of any one of the MPL, the GPL or the LGPL.
  36.  *
  37.  * ***** END LICENSE BLOCK ***** */
  38.  
  39. const Cu = Components.utils;
  40.  
  41. // The XUL and XHTML namespace.
  42. const XUL_NS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
  43. const XHTML_NS = "http://www.w3.org/1999/xhtml";
  44.  
  45. const HUD_STRINGS_URI = "chrome://browser/locale/devtools/webconsole.properties";
  46.  
  47.  
  48. Cu.import("resource://gre/modules/Services.jsm");
  49. Cu.import("resource://gre/modules/XPCOMUtils.jsm");
  50.  
  51. XPCOMUtils.defineLazyGetter(this, "stringBundle", function () {
  52.   return Services.strings.createBundle(HUD_STRINGS_URI);
  53. });
  54.  
  55.  
  56. var EXPORTED_SYMBOLS = ["AutocompletePopup"];
  57.  
  58. /**
  59.  * Autocomplete popup UI implementation.
  60.  *
  61.  * @constructor
  62.  * @param nsIDOMDocument aDocument
  63.  *        The document you want the popup attached to.
  64.  */
  65. function AutocompletePopup(aDocument)
  66. {
  67.   this._document = aDocument;
  68.  
  69.   // Reuse the existing popup elements.
  70.   this._panel = this._document.getElementById("webConsole_autocompletePopup");
  71.   if (!this._panel) {
  72.     this._panel = this._document.createElementNS(XUL_NS, "panel");
  73.     this._panel.setAttribute("id", "webConsole_autocompletePopup");
  74.     this._panel.setAttribute("label",
  75.       stringBundle.GetStringFromName("Autocomplete.label"));
  76.     this._panel.setAttribute("noautofocus", "true");
  77.     this._panel.setAttribute("ignorekeys", "true");
  78.     this._panel.setAttribute("level", "top");
  79.  
  80.     let mainPopupSet = this._document.getElementById("mainPopupSet");
  81.     if (mainPopupSet) {
  82.       mainPopupSet.appendChild(this._panel);
  83.     }
  84.     else {
  85.       this._document.documentElement.appendChild(this._panel);
  86.     }
  87.  
  88.     this._list = this._document.createElementNS(XUL_NS, "richlistbox");
  89.     this._list.flex = 1;
  90.     this._panel.appendChild(this._list);
  91.  
  92.     // Open and hide the panel, so we initialize the API of the richlistbox.
  93.     this._panel.width = 1;
  94.     this._panel.height = 1;
  95.     this._panel.openPopup(null, "overlap", 0, 0, false, false);
  96.     this._panel.hidePopup();
  97.     this._panel.width = "";
  98.     this._panel.height = "";
  99.   }
  100.   else {
  101.     this._list = this._panel.firstChild;
  102.   }
  103. }
  104.  
  105. AutocompletePopup.prototype = {
  106.   _document: null,
  107.   _panel: null,
  108.   _list: null,
  109.  
  110.   /**
  111.    * Open the autocomplete popup panel.
  112.    *
  113.    * @param nsIDOMNode aAnchor
  114.    *        Optional node to anchor the panel to.
  115.    */
  116.   openPopup: function AP_openPopup(aAnchor)
  117.   {
  118.     this._panel.openPopup(aAnchor, "after_start", 0, 0, false, false);
  119.  
  120.     if (this.onSelect) {
  121.       this._list.addEventListener("select", this.onSelect, false);
  122.     }
  123.  
  124.     if (this.onClick) {
  125.       this._list.addEventListener("click", this.onClick, false);
  126.     }
  127.  
  128.     this._updateSize();
  129.   },
  130.  
  131.   /**
  132.    * Hide the autocomplete popup panel.
  133.    */
  134.   hidePopup: function AP_hidePopup()
  135.   {
  136.     this._panel.hidePopup();
  137.  
  138.     if (this.onSelect) {
  139.       this._list.removeEventListener("select", this.onSelect, false);
  140.     }
  141.  
  142.     if (this.onClick) {
  143.       this._list.removeEventListener("click", this.onClick, false);
  144.     }
  145.   },
  146.  
  147.   /**
  148.    * Check if the autocomplete popup is open.
  149.    */
  150.   get isOpen() {
  151.     return this._panel.state == "open";
  152.   },
  153.  
  154.   /**
  155.    * Destroy the object instance. Please note that the panel DOM elements remain
  156.    * in the DOM, because they might still be in use by other instances of the
  157.    * same code. It is the responsability of the client code to perform DOM
  158.    * cleanup.
  159.    */
  160.   destroy: function AP_destroy()
  161.   {
  162.     if (this.isOpen) {
  163.       this.hidePopup();
  164.     }
  165.     this.clearItems();
  166.  
  167.     this._document = null;
  168.     this._list = null;
  169.     this._panel = null;
  170.   },
  171.  
  172.   /**
  173.    * Get the autocomplete items array.
  174.    *
  175.    * @return array
  176.    *         The array of autocomplete items.
  177.    */
  178.   getItems: function AP_getItems()
  179.   {
  180.     let items = [];
  181.  
  182.     Array.forEach(this._list.childNodes, function(aItem) {
  183.       items.push(aItem._autocompleteItem);
  184.     });
  185.  
  186.     return items;
  187.   },
  188.  
  189.   /**
  190.    * Set the autocomplete items list, in one go.
  191.    *
  192.    * @param array aItems
  193.    *        The list of items you want displayed in the popup list.
  194.    */
  195.   setItems: function AP_setItems(aItems)
  196.   {
  197.     this.clearItems();
  198.     aItems.forEach(this.appendItem, this);
  199.  
  200.     // Make sure that the new content is properly fitted by the XUL richlistbox.
  201.     if (this.isOpen) {
  202.       // We need the timeout to allow the content to reflow. Attempting to
  203.       // update the richlistbox size too early does not work.
  204.       this._document.defaultView.setTimeout(this._updateSize.bind(this), 1);
  205.     }
  206.   },
  207.  
  208.   /**
  209.    * Update the panel size to fit the content.
  210.    *
  211.    * @private
  212.    */
  213.   _updateSize: function AP__updateSize()
  214.   {
  215.     this._list.width = this._panel.clientWidth +
  216.                        this._scrollbarWidth;
  217.   },
  218.  
  219.   /**
  220.    * Clear all the items from the autocomplete list.
  221.    */
  222.   clearItems: function AP_clearItems()
  223.   {
  224.     while (this._list.hasChildNodes()) {
  225.       this._list.removeChild(this._list.firstChild);
  226.     }
  227.  
  228.     // Reset the panel and list dimensions. New dimensions are calculated when a
  229.     // new set of items is added to the autocomplete popup.
  230.     this._list.width = "";
  231.     this._list.height = "";
  232.     this._panel.width = "";
  233.     this._panel.height = "";
  234.     this._panel.top = "";
  235.     this._panel.left = "";
  236.   },
  237.  
  238.   /**
  239.    * Getter for the index of the selected item.
  240.    *
  241.    * @type number
  242.    */
  243.   get selectedIndex() {
  244.     return this._list.selectedIndex;
  245.   },
  246.  
  247.   /**
  248.    * Setter for the selected index.
  249.    *
  250.    * @param number aIndex
  251.    *        The number (index) of the item you want to select in the list.
  252.    */
  253.   set selectedIndex(aIndex) {
  254.     this._list.selectedIndex = aIndex;
  255.     this._list.ensureIndexIsVisible(this._list.selectedIndex);
  256.   },
  257.  
  258.   /**
  259.    * Getter for the selected item.
  260.    * @type object
  261.    */
  262.   get selectedItem() {
  263.     return this._list.selectedItem ?
  264.            this._list.selectedItem._autocompleteItem : null;
  265.   },
  266.  
  267.   /**
  268.    * Setter for the selected item.
  269.    *
  270.    * @param object aItem
  271.    *        The object you want selected in the list.
  272.    */
  273.   set selectedItem(aItem) {
  274.     this._list.selectedItem = this._findListItem(aItem);
  275.     this._list.ensureIndexIsVisible(this._list.selectedIndex);
  276.   },
  277.  
  278.   /**
  279.    * Append an item into the autocomplete list.
  280.    *
  281.    * @param object aItem
  282.    *        The item you want appended to the list. The object must have a
  283.    *        "label" property which is used as the displayed value.
  284.    */
  285.   appendItem: function AP_appendItem(aItem)
  286.   {
  287.     let description = this._document.createElementNS(XUL_NS, "description");
  288.     description.textContent = aItem.label;
  289.  
  290.     let listItem = this._document.createElementNS(XUL_NS, "richlistitem");
  291.     listItem.appendChild(description);
  292.     listItem._autocompleteItem = aItem;
  293.  
  294.     this._list.appendChild(listItem);
  295.   },
  296.  
  297.   /**
  298.    * Find the richlistitem element that belongs to an item.
  299.    *
  300.    * @private
  301.    *
  302.    * @param object aItem
  303.    *        The object you want found in the list.
  304.    *
  305.    * @return nsIDOMNode|null
  306.    *         The nsIDOMNode that belongs to the given item object. This node is
  307.    *         the richlistitem element.
  308.    */
  309.   _findListItem: function AP__findListItem(aItem)
  310.   {
  311.     for (let i = 0; i < this._list.childNodes.length; i++) {
  312.       let child = this._list.childNodes[i];
  313.       if (child._autocompleteItem == aItem) {
  314.         return child;
  315.       }
  316.     }
  317.     return null;
  318.   },
  319.  
  320.   /**
  321.    * Remove an item from the popup list.
  322.    *
  323.    * @param object aItem
  324.    *        The item you want removed.
  325.    */
  326.   removeItem: function AP_removeItem(aItem)
  327.   {
  328.     let item = this._findListItem(aItem);
  329.     if (!item) {
  330.       throw new Error("Item not found!");
  331.     }
  332.     this._list.removeChild(item);
  333.   },
  334.  
  335.   /**
  336.    * Getter for the number of items in the popup.
  337.    * @type number
  338.    */
  339.   get itemCount() {
  340.     return this._list.childNodes.length;
  341.   },
  342.  
  343.   /**
  344.    * Select the next item in the list.
  345.    *
  346.    * @return object
  347.    *         The newly selected item object.
  348.    */
  349.   selectNextItem: function AP_selectNextItem()
  350.   {
  351.     if (this.selectedIndex < (this.itemCount - 1)) {
  352.       this.selectedIndex++;
  353.     }
  354.     else {
  355.       this.selectedIndex = -1;
  356.     }
  357.  
  358.     return this.selectedItem;
  359.   },
  360.  
  361.   /**
  362.    * Select the previous item in the list.
  363.    *
  364.    * @return object
  365.    *         The newly selected item object.
  366.    */
  367.   selectPreviousItem: function AP_selectPreviousItem()
  368.   {
  369.     if (this.selectedIndex > -1) {
  370.       this.selectedIndex--;
  371.     }
  372.     else {
  373.       this.selectedIndex = this.itemCount - 1;
  374.     }
  375.  
  376.     return this.selectedItem;
  377.   },
  378.  
  379.   /**
  380.    * Determine the scrollbar width in the current document.
  381.    *
  382.    * @private
  383.    */
  384.   get _scrollbarWidth()
  385.   {
  386.     if (this.__scrollbarWidth) {
  387.       return this.__scrollbarWidth;
  388.     }
  389.  
  390.     let hbox = this._document.createElementNS(XUL_NS, "hbox");
  391.     hbox.setAttribute("style", "height: 0%; overflow: hidden");
  392.  
  393.     let scrollbar = this._document.createElementNS(XUL_NS, "scrollbar");
  394.     scrollbar.setAttribute("orient", "vertical");
  395.     hbox.appendChild(scrollbar);
  396.  
  397.     this._document.documentElement.appendChild(hbox);
  398.     this.__scrollbarWidth = scrollbar.clientWidth;
  399.     this._document.documentElement.removeChild(hbox);
  400.  
  401.     return this.__scrollbarWidth;
  402.   },
  403. };
  404.  
  405.